package gtkwire;

import gtkwire.widget.GtkWidget;
import gtkwire.widget.GtkWindow;
import gtkwire.listener.connector.*;
import gtkwire.listener.*;
import gtkwire.widget.utils.GtkWindowType;
import java.util.Vector;

public class GTKWireApp // implements Runnable
{
	public static final int DATA_NULL = -1;
	public static final int DATA_STRING = 0;
	public static final int DATA_INT = 1;

	/**
	*This object is used as a combined lock for gtk_main() loop and java threads that try to access gtk. Method used to achieve 
	*threadsafe locking was described by java-gnome programmer Andrew Cowie.
	*/
	private Object gtkLock = new Object();

	private Thread gtkThread = null;
	/**
	*Path to .glade file that describes user interface if applacation initilized with .glade file.
	*/
	private String gladeFilePath = null;
	private GtkWindow appWindow = null;
	private static boolean gtkInitCalled = false;
	
	/**
	* Singleton instance for all native side communication.
	*/
	private static GTKWireApp instance = null;

	/**
	*If true all incoming messages are printed.
	*/
	private boolean printMessage = false;

	/**
	*Vector of all message listeners.
	*/
	private Vector<FilteringGTKMessageListener> listeners = new Vector<FilteringGTKMessageListener>();

	private Thread commanRequestThread = null;
	private Object commandRequestLock = new Object();
	private GTKWireCommand commandRequestCommand = null;
	private GTKWireMessage commandRequestAnswer = null;
	private FilteringGTKMessageListener commandRequestFilter = null;
	private long requestSendTime = -1;
	private long REQUEST_MAX_DELAY = 100;
	protected String crWidgetType = null;
	protected String crWidgetName = null;
	protected Object crObject = null;
	protected GTKWireCommandRequest crCommand = null;

	//--------------------------------------------- CONSTRUCTORS
	/**	
	* Constructor for user created window application.
	*/	
	private GTKWireApp(){}

	//--------------------------------------------- LIBRARY CONNECTION
	public static void loadNativeLib( String path )
	{
		System.load(path);
	}

	//----------------------------------------------- APPLICATION CREATION
	private void gtkInit()
	{
		gtkInitCalled = true;
		if( gtkLock == null ) System.out.println( "gtkLock == null" );
		synchronized( gtkLock )
		{
			gtkInitFromJava(gtkLock);
		}
	}

	/**
	* This method MUST BE CALLED BEFORE ANY OTHER method call or object creation in gtkwire library.
	*/
	public static GTKWireApp initGTKApplication()
	{
		//--- Some info for bad init?
		if( instance != null ) System.exit( 1 );
		instance = new GTKWireApp();
		instance.gtkInit();
		return instance;
	}

	/**
	* Access to the singleton instance.
	*/
	public static GTKWireApp instance(){ return instance; }

	/**
	* Creates a GTK+ application from .glade file defined by path. Displays main window and starts GTK+ main loop.
	*/
	public void startGladeGTKApp( String absoluteGladePath  )
	{
		gladeFilePath = absoluteGladePath;
	}

	/**
	* Displays main window and starts GTK+ main loop.
	*/
	public void startGTKApp( GtkWindow window )
	{
		if( appWindow != null ) System.exit( 1 );
		appWindow = window;
		runGTK();
	}

	/**
	* Stops GTK+ main loop.
	*/
	public void shutdownApp()
	{
		stopApp();
		instance = null;
		gtkThread = null;
	}

	/** 
	*Do NOT call this directly.
	*/
	private void runGTK()
	{
		try
		{
			if( gladeFilePath != null )
				startApp( gladeFilePath );
			else 
				if( appWindow != null )
					startWindowApp( appWindow.getName() );
				else System.exit( 1 );//--- some info?
		}
		catch( Exception e ){}//is a hack to stop cleanly on release lock fail
	}

	//---------------------------------------------------------- NATIVE METHODS
	private native void gtkInitFromJava( Object lock );
	private native void startApp( String gladeFileName );
	private native void startWindowApp( String windowName );
	private native void stopApp();
	
	private native void sendCommand( String widgetName, String command, Object data );
	private native void sendCommand( String widgetName, String command, String data );
	private native void sendCommand( String widgetName, String command, String[] data );
	private native void sendCommand( String widgetName, String command, int data );
	private native void sendCommand( String widgetName, String command, int[] data );
	private native void sendCommand( String widgetName, String command, double data );
	private native void sendCommand( String widgetName, String command, double[] data );
	private native void sendCommand( String widgetName, String command, float[] data );
	private native void sendCommand( String widgetName, String command, boolean data );
	private native void sendCommand( String widgetName, String command, String[] dataS, int[] dataI );

	private native void createGTKObject( String type, String name, int type_hash, String[] data );
	private native void connectGTKSignal( String name, String connectSignal );
	private native void createGObjectN( int type, int id, String[] data);
	private native void createGObjectND( int type, int id, double[] data);
	private native void setGObjectPropertyN( int g_id, String property, int property_type, String value);

	//--------------------------------------------------------- MESSAGE AND COMMAND INTERFACE
	//--- Send command to gtk app.
	/*Object*/
	/**
	* Sends command to a native object with Object data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, Object data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );	
		}
	}
	/*String*/
	/**
	* Sends command to a native object with String data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, String data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*String[]*/
	/**
	* Sends command to a native object with String[] data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, String[] data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*int*/
	/**
	* Sends command to a native object with int data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, int data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*int[]*/
	/**
	* Sends command to a native object with int[] data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, int[] data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*double*/
	/**
	* Sends command to a native object with double data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, double data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*double[]*/
	/**
	* Sends command to a native object with double[] data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, double[] data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*double[]*/
	/**
	* Sends command to a native object with double[] data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, float[] data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/*boolean*/
	/**
	* Sends command to a native object with boolean data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, boolean data )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), data );
		}
	}
	/**
	* Sends command to a native object with no data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command )
	{
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), new Object() );
		}
	}
	/**
	* Sends command to a native object with String[] and int[] data.
	*/
	public void sendCommand( String widgetName, GTKWireCommand command, String[] dataS, int[] dataI )
	{	
		synchronized( gtkLock )
		{
			sendCommand( widgetName, command.getString(), dataS, dataI );
		}
	}

	//----------------------------------------------------- MESSAGES FROM GTK+
	//--- Receive message from native lib.
	/**
	*  This is called from native code to deliver GTK+ signals.
	*/
	public void gtkwireMessage( GTKWireMessage msg )
	{
		if( commandRequestFilter != null )
			commandRequestFilter.message( msg );

		for( int i = 0; i < listeners.size(); i++ )
		{
			FilteringGTKMessageListener l = listeners.elementAt( i );
			l.message( msg );
		}
	
		if( printMessage ) System.out.println( msg.toString() );
	}

	/**
	* If set true all received messages are printed into System.out.
	*/
	public void setPrintMessage( boolean b ){ printMessage = b; }


	//------------------------------------------------------- OBJECT creation
	/**
	* Creates a native GTK+ widget.
	*/
	public void createGTKWidget(String type, String name, String[] data )
	{
		synchronized( gtkLock )
		{
			createGTKObject( type, name, type.hashCode(), data );
		}	
	}

	/**
	* Connects native GTK+ widget with defined signals.
	*/	
	public void connectSignals( GtkWidget widget, Vector<GTKWireSignal> signals )
	{
		synchronized( gtkLock )
		{
			for( GTKWireSignal sig : signals )
				connectGTKSignal( widget.getName(), sig.signalName() );
		}
	}

	/**
	* Creates a native GObject.
	*/
	public void createGObject( int type, int id, String[] data )
	{
		//--- unsynchronized, because no GDK layer involvement(?)

		 createGObjectN( type, id, data );
	}

	/**
	* Creates a native GObject.
	*/
	public void createGObject( int type, int id, double[] data )
	{
		//--- unsynchronized, because no GDK layer involvement(?)
		 createGObjectND( type, id, data );
	}
	
	/**
	* Sets a property of native a GObject.
	*/
	public void setGObjectProperty( int g_id, String property, int property_type, String value)
	{
		setGObjectPropertyN( g_id, property, property_type, value);
	}

	//-------------------------------------------------------- COMMAND REQUESTS
	//--- NOTE: only objects are accepted her, not int, double etc..
	/**	
	* Sends command request into native side of and blocks to wait for an answer or a timeout.
	*/
	public GTKWireMessage sendCommandRequest( String widgetName, String widgetType, GTKWireCommandRequest cr, Object data )
	{
		synchronized( commandRequestLock )
		{
			commandRequestAnswer = null;
			commandRequestFilter = new FilteringGTKMessageListener( null )
						{
							public void filteredMessage( GTKWireMessage msg )
							{
								commandRequestAnswer = msg;
							}
				
						};
			commandRequestFilter.addFilteringName( widgetName );
			commandRequestFilter.addFilteringWidgetType( widgetType );
			commandRequestFilter.setMatchMode( FilteringGTKMessageListener.MUST_MATCH_NAME_AND_WIDGET );

			crWidgetType = widgetType;// for runtime created filter
			crWidgetName = widgetName;
			crObject = data;
			crCommand = cr;
	
			GTKWireCommandRequestThread crrunnable = new GTKWireCommandRequestThread();
			commanRequestThread = new Thread( crrunnable );
			commanRequestThread.start();
			boolean timeout = false;
			while( true )
			{
				if( commandRequestAnswer != null ) break;
				if( requestSendTime != -1 && ( ( System.currentTimeMillis() - requestSendTime ) > REQUEST_MAX_DELAY ) )
				{
					timeout = true;
					break;
				}
				Thread.yield();
			}
			requestSendTime = -1;
			//--- something else here in the future
			if( timeout ) System.out.println("---------------------- TIMEOUT! --------------------------------------" );
	
			commanRequestThread = null;
			commandRequestFilter = null;

			GTKWireMessage rmsg = commandRequestAnswer;
			commandRequestAnswer = null;
			return rmsg;
		}//end commandRequestLock
	}
	/**
	* Used to detect timeouts for command requests.
	*/
	public void startRequestTime()
	{
		requestSendTime = System.currentTimeMillis();
	}
	
	//-------------------------------------------------------- LISTENERS
	//--- Listeners for message handling.
	/**
	* Adds a listener for certain type of messages. This is usually called from widgets addXListener() methods.
	*/
	public void addListener( FilteringGTKMessageListener l )
	{
		//-- tää matsäys homma dunkkaa viela, liian simppeli

		Object filteredListener = l.getListener();
 		Vector<FilteringGTKMessageListener> matches = getFiltersForListener( filteredListener );
		boolean combined = false;
		for( int i = 0; i < matches.size(); i++ )
		{
			FilteringGTKMessageListener oldl = matches.elementAt( i );
			if( oldl.getClass().getName().equals( l.getClass().getName() ) )
			{
				oldl.combine( l );
				combined = true;
			}
		}
		
		if( !combined ) listeners.add( l );
	}

	/**
	* Used to combine filters with same listener object.
	*/
	public Vector<FilteringGTKMessageListener> getFiltersForListener( Object l )
	{
		Vector<FilteringGTKMessageListener> rvec = new Vector();
		
		for( int i = 0; i < listeners.size(); i++ )
		{
			FilteringGTKMessageListener fl = listeners.elementAt( i );
			if( fl.getListener() == l )
				rvec.add( fl );
		}
		return rvec;
	}

}//end class
